【译】Binder 和 WindowToken

原文链接:https://www.androiddesignpatterns.com/2013/07/binders-window-tokens.html

前言

  • Android 系统的核心设计目标之一是提供一个开放平台,这个平台不依赖中央机构来验证应用程序是不是真像它们对外宣称的那样运行。 为了实现这个目标,Android 使用应用程序沙箱以及 Linux 进程隔离 来防止应用程序以不受控制或者不安全的方式访问系统或其他应用程序 。 选择该体系结构时要同时考虑开发人员和设备用户:双方都不需要为此采用额外的步骤来 保护设备免受恶意应用程序的侵害。 系统应该帮我们自动处理好这一切。
  • 很长时间以来,我一直将这种安全看成是理所当然的,而没有完全了解它的实际运行方式。 但是最近我变得好奇了。 哪种机制能够阻止我干下面这些坏事呢。例如,欺骗系统让它释放另一个应用程序获取的唤醒锁,或者是把另一个应用程序的 Window 从屏幕隐藏掉? 更普遍地,Android 的核心系统服务 是怎么实现既高效又安全地响应第三方应用程序发出的请求的?
  • 让我吃惊的是,几乎所有问题的答案都非常简单:Binder。 Binders 是 Android 体系结构的基石; 他们从开发人员那里抽象 IPC 的底层细节,让应用程序可以很简单地跟系统服务 以及其他远程服务组件进行通信。 但是 Binders 还有许多其他很酷的特性,它们以各种巧妙的方式在整个系统中得到了广泛使用,使框架能够更容易解决安全问题。 这篇文章主要详细介绍其中的一个特需特性——Binder Tokens

Binder Tokens

  • Binder 对象的一个有趣特性是,每个 binder 实例在系统中所有进程之间都保持唯一的身份,无论它跨越多少进程边界或到达何处。 这个功能是 Binder 内核驱动程序提供的,这个驱动程序分析每个 Binder 事务的内容,并为其看到的每个 Binder 对象分配一个唯一的 32 位整数值。 为了确保 Java 的==运算符遵守 Binder 唯一的,跨进程的对象标识协定,对 Binder 的对象引用的对待与其他对象的对待有所不同。 具体来说,每个 Binder 的对象引用都被分配了
    • 1、指向同一进程中的 Binder 对象的虚拟内存地址,或者
    • 2、唯一的 32 位句柄(由 Binder 内核驱动程序分配),在不同的过程中指向 Binder 的虚拟内存地址。
  • Binder 内核驱动程序会为其看到的每个 Binder 维护本地地址到远程 Binder 句柄的映射(反之亦然),并为每个 Binder 的对象引用分配适当的值,以确保即使在远程进程中,equals 方法的行为能够符合预期
  • Binder 拥有唯一对象身份。这一条规则使得它们可以用于特殊目的:作为共享的安全访问令牌。
    • Binder 是全局唯一的,这意味着如果创建一个 Binder 对象,则没有其他人可以创建与它相等的一个对象,(世界上没有完全相同的两片叶子,也没有完全相同的两个 Binder 对象)。 因此,app framework 广泛使用 Binder token,以确保协作进程之间的安全交互:客户端可以创建一个 Binder 对象作为跟 server 进程共享的令牌,并且 server 进程可以用它来验证 客户端的请求,而不会被其他人欺骗

WindowToken

  • 如果你之前看 Android 中 View 类的官方文档,你可能偶然发现了 getWindowToken 方法,并且会好奇它的含义是什么。 顾名思义,window token 是 Binder tokens 的一种特殊类型,Window Manager 使用它来唯一地标识系统中的 Window 。 window token 对于安全性很重要,因为它们使恶意应用程序无法在其他应用程序的窗口上绘制。 window Manager 通过要求应用程序将它的 window token 作为每个添加或删除窗口的请求的参数来保证 上述问题不会发生。如果 token 不匹配,则 window manager 会拒绝该请求并抛出 BadTokenException 异常。 如果没有 window token ,则无法执行此必要的识别步骤,并且 window manager 将无法保护自己免受恶意应用程序的攻击
  • 看到这,你可能会想知道,实际开发中有哪些场景需要用到 window token。下面举几个例子 👇
    • 1、当应用程序首次启动时,AMS 会创建一种特殊的窗口令牌,称为应用程序窗口令牌,它唯一地标识应用程序的顶级容器窗口。AMS 将此令牌同时提供给 app 和 WMS,并且应用程序每次要向屏幕添加新窗口时,都会将 token 发送给 WMS 。 这样可以确保应用程序和 WMS 之间的安全交互(让它无法在其他应用程序之上添加窗口),并使 AMS 可以轻松地直接向 WMS 发出请求。 例如,AMS 可以说“隐藏此令牌的所有窗口”,然后 WMS 将能够正确识别应关闭的一组窗口。
    • 2、实现自己的自定义 launcher 的开发人员可以通过调用 sendWallpaperCommand(IBinder windowToken,String action,int x,int y,int z,Bundle extras)方法与位于其后方的动态壁纸窗口进行交互。 为确保 launcher 以外的其他应用程序无法与动态壁纸进行交互,该框架要求开发人员将 window token 作为该方法的第一个参数传递。 如果 window token 未标识位于墙纸顶部的当前前景活动窗口,则将忽略该命令并打印警告
    • 3、应用程序可以通过调用 hideSoftInputFromWindow(IBinder windowToken,int flags)方法来要求 InputMethodManager 隐藏软键盘,但是必须提供窗口令牌作为请求的一部分。 如果令牌与当前接受输入的窗口所属的窗口令牌不匹配,则 InputMethodManager 将拒绝该请求。 这使得恶意应用程序无法强制关闭由另一个应用程序打开的软键盘
    • 4、手动将新 window 添加到屏幕上的应用程序(也就是调用 addView(View,WindowManager.LayoutParams) 方法)可能需要通过设置 WindowManager.LayoutParams.token 字段来指定其应用程序的 window token。 因为 getWindowManager() 方法会返回一个 WindowManager,它将自动为您设置令牌的值,所以大部分普通的应用程序都不太可能会这样做。 也就是说,如果将来在某个时候遇到需要从后台服务向屏幕添加 panel window 的场景,需要通过应用程序 window token 手动签署请求才能实现

      总结

尽管 Binder Tokens 的存在对开发人员来说几乎是透明的,但是为了保障安全, Binder Token 在系统中被广泛应用。 Android 是一个大规模的协作进程分布式系统,它依赖于 Binder 对象在设备上所有进程之间都是唯一的。 Binder Tokens 是框架中各种交互 背后的驱动力,如果没有它们,那么 app 进程与系统进程之间的安全通信将很难实现。

参考文献

  • 1、这个文档实际上暗示了 Binders 可以这么用:“只需直接实例化一个原始的 Binder 对象,就可以把它用作可在各个进程之间共享的令牌。”
    https://developer.android.com/reference/android/os/Binder.html
  • 2、在 frameworks/base/services/java/com/android/server 目录下 随机选择一个文件,这个文件很有可能会以某种形式使用 Binder token。另一个很酷的例子涉及状态栏、通知管理器和系统 UI。具体而言,StatusBarManagerService 维护一个 binder token 到 Notification 的全局映射。当 NotificationManagerService 向状态栏管理器( status bar manager)请求向状态栏添加通知时,状态栏管理器会创建一个能够惟一标识这个 通知 的 binder token ,并将其传递给通知管理器和 SystemUI。由于这三个角色都知道通知的 binder token ,因此从那之后,对通知的任何更改(例如通知管理器取消通知,或者 SystemUI 检测到用户从屏幕上滑动了一个通知)都将首先经过 状态栏管理器。这使得三个系统服务更容易保持同步: 状态栏管理器可以负责集中所有关于当前应该显示哪些通知的信息,而不需要 SystemUI 和 notification manager 之间直接进行交互
  • 3、拥有 android.permission.SYSTEM_ALERT_WINDOW 权限(又称“从其他应用上绘图”权限)的应用程序是此规则的明显例外。 Facebook Messenger 和 DicePlayer 是两个流行的应用程序,需要此权限,并使用它在后台服务的其他应用程序之上添加窗口。Facebook Messenger 和 DicePlayer 是两个流行的应用程序,需要此权限,并使用它在后台服务的其他应用程序之上添加窗口
  • 4 。ActivityManagerService 是全局系统服务(在 System Server 进程中运行),它负责启动(和管理)新组件,例如“Activity 和 Service”。 它还涉及 维护内核中 low-memory handler 使用的 OOM 调整,权限,任务管理等
  • 5、你可以通过调用 getApplicationWindowToken() 来获得一个引用
    https://developer.android.com/reference/android/view/View#getApplicationWindowToken()
  • 6、这个解释仅仅触及表面。有关 SurfaceFlinger、WindowManager 和应用程序之间如何交互的更详细的解释,请参阅本文。「SurfaceFlinger 和 Hardware Composer」一节的第三段简要地提到了传递给应用程序的 Binder 应用程序 window token。
    http://source.android.com/devices/graphics/architecture.htm
Show Comments
0%